package com.landinfo.service;

import com.landinfo.domain.Authority;
import com.landinfo.domain.Region2;
import com.landinfo.domain.User;
import com.landinfo.repository.AuthorityRepository;
import com.landinfo.config.Constants;
import com.landinfo.repository.UserRepository;
import com.landinfo.security.AuthoritiesConstants;
import com.landinfo.security.SecurityUtils;
import com.landinfo.service.util.RandomUtil;
import com.landinfo.service.dto.UserDTO;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import java.time.Instant;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;

/**
 * Service class for managing users.
 */
//@Service
//@Transactional
//public class UserService {
//
//    private final Logger log = LoggerFactory.getLogger(UserService.class);
//
//    private final UserRepository userRepository;
//
//    private final PasswordEncoder passwordEncoder;
//
//    private final AuthorityRepository authorityRepository;
//
//    public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder, AuthorityRepository authorityRepository) {
//        this.userRepository = userRepository;
//        this.passwordEncoder = passwordEncoder;
//        this.authorityRepository = authorityRepository;
//    }
//
//    public Optional<User> activateRegistration(String key) {
//        log.debug("Activating user for activation key {}", key);
//        return userRepository.findOneByActivationKey(key)
//            .map(user -> {
//                // activate given user for the registration key.
//                user.setActivated(true);
//                user.setActivationKey(null);
//                log.debug("Activated user: {}", user);
//                return user;
//            });
//    }
//
//    public Optional<User> completePasswordReset(String newPassword, String key) {
//       log.debug("Reset user password for reset key {}", key);
//
//       return userRepository.findOneByResetKey(key)
//           .filter(user -> user.getResetDate().isAfter(Instant.now().minusSeconds(86400)))
//           .map(user -> {
//                user.setPassword(passwordEncoder.encode(newPassword));
//                user.setResetKey(null);
//                user.setResetDate(null);
//                return user;
//           });
//    }
//
//    public Optional<User> requestPasswordReset(String mail) {
//        return userRepository.findOneByEmail(mail)
//            .filter(User::getActivated)
//            .map(user -> {
//                user.setResetKey(RandomUtil.generateResetKey());
//                user.setResetDate(Instant.now());
//                return user;
//            });
//    }
//
//    public User createUser(String login, String password, String firstName, String lastName, String email,
//        String imageUrl, String langKey) {
//
//        User newUser = new User();
//        Authority authority = authorityRepository.findOne(AuthoritiesConstants.USER);
//        Set<Authority> authorities = new HashSet<>();
//        String encryptedPassword = passwordEncoder.encode(password);
//        newUser.setLogin(login);
//        // new user gets initially a generated password
//        newUser.setPassword(encryptedPassword);
//        newUser.setFirstName(firstName);
//        newUser.setLastName(lastName);
//        newUser.setEmail(email);
//        newUser.setImageUrl(imageUrl);
//        newUser.setLangKey(langKey);
//        // new user is not active
//        newUser.setActivated(false);
//        // new user gets registration key
//        newUser.setActivationKey(RandomUtil.generateActivationKey());
//        authorities.add(authority);
//        newUser.setAuthorities(authorities);
//        userRepository.save(newUser);
//        log.debug("Created Information for User: {}", newUser);
//        return newUser;
//    }
//
//    public User createUser(UserDTO userDTO) {
//        User user = new User();
//        user.setLogin(userDTO.getLogin());
//        user.setFirstName(userDTO.getFirstName());
//        user.setLastName(userDTO.getLastName());
//        user.setEmail(userDTO.getEmail());
//        user.setImageUrl(userDTO.getImageUrl());
//        if (userDTO.getLangKey() == null) {
//            user.setLangKey("en"); // default language
//        } else {
//            user.setLangKey(userDTO.getLangKey());
//        }
//        if (userDTO.getAuthorities() != null) {
//            Set<Authority> authorities = new HashSet<>();
//            userDTO.getAuthorities().forEach(
//                authority -> authorities.add(authorityRepository.findOne(authority))
//            );
//            user.setAuthorities(authorities);
//        }
//        String encryptedPassword = passwordEncoder.encode(RandomUtil.generatePassword());
//        user.setPassword(encryptedPassword);
//        user.setResetKey(RandomUtil.generateResetKey());
//        user.setResetDate(Instant.now());
//        user.setActivated(true);
//        userRepository.save(user);
//        log.debug("Created Information for User: {}", user);
//        return user;
//    }
//
//    /**
//     * Update basic information (first name, last name, email, language) for the current user.
//     *
//     * @param firstName first name of user
//     * @param lastName last name of user
//     * @param email email id of user
//     * @param langKey language key
//     * @param imageUrl image URL of user
//     */
//    public void updateUser(String firstName, String lastName, String email, String langKey, String imageUrl) {
//        userRepository.findOneByLogin(SecurityUtils.getCurrentUserLogin()).ifPresent(user -> {
//            user.setFirstName(firstName);
//            user.setLastName(lastName);
//            user.setEmail(email);
//            user.setLangKey(langKey);
//            user.setImageUrl(imageUrl);
//            log.debug("Changed Information for User: {}", user);
//        });
//    }
//
//    /**
//     * Update all information for a specific user, and return the modified user.
//     *
//     * @param userDTO user to update
//     * @return updated user
//     */
//    public Optional<UserDTO> updateUser(UserDTO userDTO) {
//        return Optional.of(userRepository
//            .findOne(userDTO.getId()))
//            .map(user -> {
//                user.setLogin(userDTO.getLogin());
//                user.setFirstName(userDTO.getFirstName());
//                user.setLastName(userDTO.getLastName());
//                user.setEmail(userDTO.getEmail());
//                user.setImageUrl(userDTO.getImageUrl());
//                user.setActivated(userDTO.isActivated());
//                user.setLangKey(userDTO.getLangKey());
//                Set<Authority> managedAuthorities = user.getAuthorities();
//                managedAuthorities.clear();
//                userDTO.getAuthorities().stream()
//                    .map(authorityRepository::findOne)
//                    .forEach(managedAuthorities::add);
//                log.debug("Changed Information for User: {}", user);
//                return user;
//            })
//            .map(UserDTO::new);
//    }
//
//    public void deleteUser(String login) {
//        userRepository.findOneByLogin(login).ifPresent(user -> {
//            userRepository.delete(user);
//            log.debug("Deleted User: {}", user);
//        });
//    }
//
//    public void changePassword(String password) {
//        userRepository.findOneByLogin(SecurityUtils.getCurrentUserLogin()).ifPresent(user -> {
//            String encryptedPassword = passwordEncoder.encode(password);
//            user.setPassword(encryptedPassword);
//            log.debug("Changed password for User: {}", user);
//        });
//    }
//
//    @Transactional(readOnly = true)
//    public Page<UserDTO> getAllManagedUsers(Pageable pageable) {
//        return userRepository.findAllByLoginNot(pageable, Constants.ANONYMOUS_USER).map(UserDTO::new);
//    }
//
//    @Transactional(readOnly = true)
//    public Optional<User> getUserWithAuthoritiesByLogin(String login) {
//        return userRepository.findOneWithAuthoritiesByLogin(login);
//    }
//
//    @Transactional(readOnly = true)
//    public User getUserWithAuthorities(Long id) {
//        return userRepository.findOneWithAuthoritiesById(id);
//    }
//
//    @Transactional(readOnly = true)
//    public User getUserWithAuthorities() {
//        return userRepository.findOneWithAuthoritiesByLogin(SecurityUtils.getCurrentUserLogin()).orElse(null);
//    }
//
//
//    /**
//     * Not activated users should be automatically deleted after 3 days.
//     * <p>
//     * This is scheduled to get fired everyday, at 01:00 (am).
//     * </p>
//     */
//    @Scheduled(cron = "0 0 1 * * ?")
//    public void removeNotActivatedUsers() {
//        List<User> users = userRepository.findAllByActivatedIsFalseAndCreatedDateBefore(Instant.now().minus(3, ChronoUnit.DAYS));
//        for (User user : users) {
//            log.debug("Deleting not activated user {}", user.getLogin());
//            userRepository.delete(user);
//        }
//    }
//
//    /**
//     * @return a list of all the authorities
//     */
//    public List<String> getAuthorities() {
//        return authorityRepository.findAll().stream().map(Authority::getName).collect(Collectors.toList());
//    }
//}
@Service
@Transactional
public class UserService {

    private final Logger log = LoggerFactory.getLogger(UserService.class);

    private final UserRepository userRepository;

    private final PasswordEncoder passwordEncoder;

    private final AuthorityRepository authorityRepository;

    @PersistenceContext
    private EntityManager entityManager;

    public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder, AuthorityRepository authorityRepository) {
        this.userRepository = userRepository;
        this.passwordEncoder = passwordEncoder;
        this.authorityRepository = authorityRepository;
    }

    public Optional<User> activateRegistration(String key) {
        log.debug("Activating user for activation key {}", key);
        return userRepository.findOneByActivationKey(key)
            .map(user -> {
                // activate given user for the registration key.
                user.setActivated(true);
                user.setActivationKey(null);
                log.debug("Activated user: {}", user);
                return user;
            });
    }

    public Optional<User> activateRegistration2(String userid) {
        log.debug("Activating user for activation userid {}", userid);
        return userRepository.findOneByLogin(userid)
            .map(user -> {
                // activate given user for the registration key.
                user.setActivated(true);
                user.setActivationKey(null);
                log.debug("Activated user: {}", user);
                return user;
            });
    }

    public Optional<User> completePasswordReset(String newPassword, String key) {
        log.debug("Reset user password for reset key {}", key);

        return userRepository.findOneByResetKey(key)
            .filter(user -> {
                ZonedDateTime oneDayAgo = ZonedDateTime.now().minusHours(24);
                //return user.getResetDate().isAfter(oneDayAgo);
                return user.getResetDate().isAfter(Instant.now().minusMillis(24 * 60));
            })
            .map(user -> {
                user.setPassword(passwordEncoder.encode(newPassword));
                user.setResetKey(null);
                user.setResetDate(null);
                return user;
            });
    }

    public Optional<User> requestPasswordReset(String mail) {
        return userRepository.findOneByEmail(mail)
            .filter(User::getActivated)
            .map(user -> {
                user.setResetKey(RandomUtil.generateResetKey());
                user.setResetDate(Instant.now());
                return user;
            });
    }

    public User createUser(String login, String password, String firstName, String lastName, String email,
                           String imageUrl, String langKey) {

        User newUser = new User();
        Authority authority = authorityRepository.findOneByName(AuthoritiesConstants.USER);
        Set<Authority> authorities = new HashSet<>();
        String encryptedPassword = passwordEncoder.encode(password);
        newUser.setLogin(login);
        // new user gets initially a generated password
        newUser.setPassword(encryptedPassword);
        newUser.setFirstName(firstName);
        newUser.setLastName(lastName);
        newUser.setEmail(email);
        newUser.setImageUrl(imageUrl);
        newUser.setLangKey(langKey);
        // new user is not active
        newUser.setActivated(false);
        // new user gets registration key
        newUser.setActivationKey(RandomUtil.generateActivationKey());
        authorities.add(authority);
        newUser.setAuthorities(authorities);
        userRepository.save(newUser);
        log.debug("Created Information for User: {}", newUser);
        return newUser;
    }

    public User createUser(UserDTO userDTO) {
        User user = new User();
        user.setLogin(userDTO.getLogin());
        user.setChineseName(userDTO.getChineseName());
        user.setFirstName(userDTO.getFirstName());
        user.setLastName(userDTO.getLastName());
        user.setEmail(userDTO.getEmail());
        user.setJob(userDTO.getJob());
        user.setTelphone(userDTO.getTelphone());
        user.setDepartment(userDTO.getDepartment());
        user.setCompany(userDTO.getCompany());
        user.setImageUrl(userDTO.getImageUrl());
        if (userDTO.getLangKey() == null) {
            user.setLangKey("zh-cn"); // default language
        } else {
            user.setLangKey(userDTO.getLangKey());
        }
        if (userDTO.getAuthorities() != null) {
            Set<Authority> authorities = new HashSet<>();
            userDTO.getAuthorities().forEach(
                authority -> authorities.add(authorityRepository.findOneByName(authority))
            );
            user.setAuthorities(authorities);
        }
        String encryptedPassword = passwordEncoder.encode(RandomUtil.generatePassword());
        user.setPassword(encryptedPassword);
        user.setResetKey(RandomUtil.generateResetKey());
        user.setResetDate(Instant.now());
        user.setActivated(true);
        if (userDTO.getRegion() != null) {
            Region2 region = new Region2();
            region.setId(userDTO.getRegion().getId());
            user.setRegion(region);
        }
//        if (userDTO.getUserProfile() != null) {
//            user.setUserProfile(userDTO.getUserProfile());
//            user.getUserProfile().setUser(user);
//        }
        user.setActivated(user.getActivated());
        user.setLangKey(user.getLangKey());
        userRepository.save(user);

        log.debug("Created Information for User: {}", user);
        return user;
    }

    /**
     * Update basic information (first name, last name, email, language) for the current user.
     */
    public void updateUser(String firstName, String lastName, String email, String langKey,String imageUrl) {
        userRepository.findOneByLogin(SecurityUtils.getCurrentUserLogin()).ifPresent(user -> {
            user.setFirstName(firstName);
            user.setLastName(lastName);
            user.setEmail(email);
            user.setLangKey(langKey);
            user.setImageUrl(imageUrl);
            log.debug("Changed Information for User: {}", user);
        });
    }

    /**
     * Update all information for a specific user, and return the modified user.
     */
    public Optional<UserDTO> updateUser(UserDTO userDTO) {
        return Optional.of(userRepository
            .findById(userDTO.getId()))
            .filter(Optional::isPresent)
            .map(Optional::get)
            .map(user -> {
                user.setLogin(userDTO.getLogin());
                user.setChineseName(userDTO.getChineseName());
                user.setFirstName(userDTO.getFirstName());
                user.setLastName(userDTO.getLastName());
                user.setEmail(userDTO.getEmail());
                user.setJob(userDTO.getJob());
                user.setTelphone(userDTO.getTelphone());
                user.setDepartment(userDTO.getDepartment());
                user.setCompany(userDTO.getCompany());
                user.setImageUrl(userDTO.getImageUrl());
                user.setActivated(userDTO.isActivated());
                user.setLangKey(userDTO.getLangKey());
                user.setLastModifiedDate(userDTO.getLastModifiedDate());
                if (userDTO.getPassword() != null) {
                    String encryptedPassword = passwordEncoder.encode(userDTO.getPassword());
                    user.setPassword(encryptedPassword);
                }
//        user.setPassword(userDTO.get);
                Set<Authority> managedAuthorities = user.getAuthorities();
                managedAuthorities.clear();
                userDTO.getAuthorities().stream()
                    .map(authorityRepository::findById)
                    .filter(Optional::isPresent)
                    .map(Optional::get)
                    .forEach(managedAuthorities::add);
                log.debug("Changed Information for User: {}", user);
                if (userDTO.getRegion() != null) {
                    Region2 region = new Region2();
                    region.setId(userDTO.getRegion().getId());
                    user.setRegion(region);
                }
                return user;
            })
            .map(UserDTO::new);
    }

    public void deleteUser(String login) {
        userRepository.findOneByLogin(login).ifPresent(user -> {
            userRepository.delete(user);
            log.debug("Deleted User: {}", user);
        });
    }

    public void deleteUserById(String id) {
        userRepository.findById(id).ifPresent(user -> {
            userRepository.delete(user);
//            this.clearUserCaches(user);
            log.debug("Deleted User: {}", user);
        });
    }

    public void changePassword(String password) {
        userRepository.findOneByLogin(SecurityUtils.getCurrentUserLogin()).ifPresent(user -> {
            String encryptedPassword = passwordEncoder.encode(password);
            user.setPassword(encryptedPassword);
            log.debug("Changed password for User: {}", user);
        });
    }

    public void changeUserPassword(String userName,String password) {
        userRepository.findOneByLogin(userName).ifPresent(user -> {
            String encryptedPassword = passwordEncoder.encode(password);
            user.setPassword(encryptedPassword);
            log.debug("Changed password for User: {}", user);
        });
    }

    @Transactional(readOnly = true)
    public List<User> getAllUsersByAuthorityId(String authorityId) {
        List<User> users = userRepository.getAllUsersByAuthorityId(authorityId);
        return users;
    }

    @Transactional(readOnly = true)
    public Page<UserDTO> getAllManagedUsers(Pageable pageable) {
        Page<User> users = userRepository.findAllByLoginNot(pageable, Constants.ANONYMOUS_USER);
        return users.map(UserDTO::new);
    }

    @Transactional(readOnly = true)
    public List<User> getChildUsers(String userId) {
        String xzqhCode = replace(userId);
        String hql = String.format("from User where substring(region.id,1,%d)='%s'", xzqhCode.length(), xzqhCode);
        Query query = entityManager.createQuery(hql);
        return query.getResultList();
    }

    @Transactional(readOnly = true)
    public List<User> getAllUsers() {
        String hql = String.format("from User");
        Query query = entityManager.createQuery(hql);
        return query.getResultList();
    }

    private String replace(String userId) {
        if (userId == null) {
            return userId;
        }
        for (int i = userId.length() - 1; i >= 0; i--) {
            if (userId.charAt(i) == '0') {
                if (userId.length() >= 1) {
                    userId = userId.substring(0, userId.length() - 1);
                } else {
                    break;
                }
            } else {
                break;
            }
        }
        return userId;
    }

    @Transactional(readOnly = true)
    public Optional<User> getUserWithAuthoritiesByLogin(String login) {
        return userRepository.findOneWithAuthoritiesByLogin(login);
    }

    @Transactional(readOnly = true)
    public User getUserWithAuthorities(Long id) {
        return userRepository.findOneWithAuthoritiesById(id);
    }

    @Transactional(readOnly = true)
    public User getUserWithAuthorities() {
        return userRepository.findOneWithAuthoritiesByLogin(SecurityUtils.getCurrentUserLogin()).orElse(null);
    }


    /**
     * Not activated users should be automatically deleted after 3 days.
     * <p>
     * This is scheduled to get fired everyday, at 01:00 (am).
     * </p>
     */
    @Scheduled(cron = "0 0 1 * * ?")
    public void removeNotActivatedUsers() {
        ZonedDateTime now = ZonedDateTime.now();
        //List<User> users = userRepository.findAllByActivatedIsFalseAndCreatedDateBefore(now.minusDays(3));
        List<User> users = userRepository.findAllByActivatedIsFalseAndCreatedDateBefore(Instant.now().minusMillis(3 * 24 * 60));
        for (User user : users) {
            log.debug("Deleting not activated user {}", user.getLogin());
            userRepository.delete(user);
        }
    }
}

